package com.karson.service.service.impl;

import cn.dev33.satoken.stp.StpUtil;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.karson.common.config.rabbitmq.MsgProducer;
import com.karson.common.config.rabbitmq.RabbitConfig;
import com.karson.common.config.redis.RedisConfig;

import com.karson.common.constants.BusinessConstants;
import com.karson.common.constants.Constants;
import com.karson.common.dto.rabbitmq.StatisticMessageDTO;

import com.karson.common.dto.rabbitmq.UserModelDTO;
import com.karson.common.model.ActionCounts;
import com.karson.common.model.TrackInfo;
import com.karson.common.model.UserAction;
import com.karson.common.model.UserTrackPlayback;
import com.karson.common.request.UserActionRequest;
import com.karson.common.util.BitMapUtils;
import com.karson.common.util.RedisUtil;
import com.karson.common.vo.ActionStatusVo;
import com.karson.service.dao.UserTrackPlaybackMapper;
import com.karson.service.service.ActionCountsService;
import com.karson.service.service.TrackInfoService;
import com.karson.service.service.UserActionService;
import com.karson.service.dao.UserActionMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
* @author ZKS
* @description 针对表【ar_user_action(用户行为记录表（点赞/点踩/收藏）)】的数据库操作Service实现
* @createDate 2025-09-04 14:08:51
*/
@Service
public class UserActionServiceImpl extends ServiceImpl<UserActionMapper, UserAction>
    implements UserActionService{
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private ActionCountsService actionCountsService;
    @Autowired
    private UserTrackPlaybackMapper userTrackPlaybackMapper;
    @Autowired
    private TrackInfoService trackInfoService;
    @Autowired
    private UserActionMapper userActionMapper;
    @Autowired
    private MsgProducer msgProducer;

    @Override
    public void recordUserAction(UserActionRequest userAction) {
        String redisKey = RedisConfig.COUNT_KEY + userAction.getType() + ":" + userAction.getMessageId();

        String actionType = userAction.getActionType();
        String userSetKey;
        String actionHashKey;
        String oppositeSetKey = null;
        String oppositeHashKey = null;
        int userId = StpUtil.getLoginIdAsInt();
        // 根据操作类型设置 Redis key
        switch (actionType) {
            case Constants.ACTION_GREAT -> {
                actionHashKey = RedisConfig.HASH_LIKE_KEY;
                userSetKey = RedisConfig.ALBUM_USER_LIKE;
                oppositeHashKey = RedisConfig.HASH_DISLIKE_KEY;
                oppositeSetKey = RedisConfig.ALBUM_USER_DISLIKE;
            }
            case Constants.ACTION_LESS -> {
                actionHashKey = RedisConfig.HASH_DISLIKE_KEY;
                userSetKey = RedisConfig.ALBUM_USER_DISLIKE;
                oppositeHashKey = RedisConfig.HASH_LIKE_KEY;
                oppositeSetKey = RedisConfig.ALBUM_USER_LIKE;
            }
            case Constants.ACTION_FAV -> {
                actionHashKey = RedisConfig.HASH_FAV_KEY;
                userSetKey = RedisConfig.ALBUM_USER_FAV;
            }
            case Constants.ACTION_CLICK -> {
                actionHashKey = RedisConfig.HASH_CLICK_KEY;
                // 不用做去重，做PV统计
                userSetKey = null;
            }
            case Constants.ACTION_PLAY -> {
                actionHashKey = RedisConfig.HASH_PLAY_KEY;
                // 去重，保证一个不管播放专辑下的哪一个单一集都只加一个播放量
                userSetKey = RedisConfig.ALBUM_USER_PLAY;
            }
            default -> {
                return; // 未知操作类型直接返回
            }
        }
        boolean hasAction = redisUtil.sIsMember(userSetKey + userId, userAction.getMessageId());
        boolean hasOpposite = redisUtil.sIsMember(oppositeSetKey + userId, userAction.getMessageId());
        // 构建消息对象
        StatisticMessageDTO dto = new StatisticMessageDTO();
        dto.setMessageId(userAction.getMessageId());
        dto.setEntityType(userAction.getType());

        // 当前操作处理
        if (hasAction) {
            // 已操作 -> 取消
            redisUtil.hincrBy(redisKey, actionHashKey, -1);
            redisUtil.removeMember(userSetKey + userId, userAction.getMessageId());
            dto.setType(actionHashKey);
            dto.setDelta(-1);
            msgProducer.sendMsg(RabbitConfig.ACTION_EXCHANGE,RabbitConfig.ROUTING_KEY_ACTION,JSONUtil.toJsonStr(dto));
        } else {
            // 新增操作
            redisUtil.hincrBy(redisKey, actionHashKey, 1);
            if (userSetKey != null) {
                redisUtil.add(userSetKey + userId, userAction.getMessageId());
            }
            dto.setType(actionHashKey);
            dto.setDelta(1);
            msgProducer.sendMsg(RabbitConfig.ACTION_EXCHANGE,RabbitConfig.ROUTING_KEY_ACTION,JSONUtil.toJsonStr(dto));
            // 互斥逻辑，仅点赞/点踩
            if (hasOpposite) {
                redisUtil.hincrBy(redisKey, oppositeHashKey, -1);
                redisUtil.removeMember(oppositeSetKey + userId, userAction.getMessageId());
                dto.setType(oppositeHashKey);
                dto.setDelta(-1);
                msgProducer.sendMsg(RabbitConfig.ACTION_EXCHANGE,RabbitConfig.ROUTING_KEY_ACTION,JSONUtil.toJsonStr(dto));
            }
        }
        if (userAction.getType().equals(BusinessConstants.BUSINESS_ALBUM)){
            UserModelDTO userModelDTO = new UserModelDTO();
            userModelDTO.setUserAction(userAction);
            userModelDTO.setUid(userId);
            userModelDTO.setHasAction(hasAction);
            msgProducer.sendMsg(RabbitConfig.ACTION_EXCHANGE,RabbitConfig.ROUTING_KEY_MODEL,JSONUtil.toJsonStr(userModelDTO));
        }
        syncDatabase(userAction,hasAction);
    }
    private void syncDatabase(UserActionRequest userAction, boolean hasAction) {
        int userId = StpUtil.getLoginIdAsInt();
        LambdaQueryWrapper<UserAction> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserAction::getMessageId, userAction.getMessageId())
                .eq(UserAction::getUid, userId)
                .eq(UserAction::getActionType, userAction.getActionType());

        UserAction ua = super.getOne(queryWrapper);
        if (ua == null && !hasAction) {
            ua = new UserAction();
            ua.setUid(userId);
            ua.setMessageId(userAction.getMessageId());
            ua.setActionType(userAction.getActionType());
            ua.setStatus(Constants.ACTION_ACCEPT);
            ua.setUpdateTime(new Date());
            super.save(ua);
        } else if (ua != null) {
            // 如果只是播放操作的话，不应该被取消只需要更改行为时间
            Boolean shouldCancel = shouldCancel(userAction);
            ua.setStatus(hasAction && !shouldCancel ? Constants.ACTION_CANCEL : Constants.ACTION_ACCEPT);
            ua.setUpdateTime(new Date());
            super.updateById(ua);
        }
    }

    /**
     * 播放操作以及点击操作的行为不应该被取消
     * @param userActionRequest
     * @return
     */
    private Boolean shouldCancel(UserActionRequest userActionRequest){
        return userActionRequest.getActionType().equals(Constants.ACTION_PLAY) || userActionRequest.getActionType().equals(Constants.ACTION_CLICK);
    }
    @Override
    public ActionStatusVo getActionStatus(Integer albumId, String type) {
        String userLikeKey = RedisConfig.ALBUM_USER_LIKE;
        String userDislikeKey = RedisConfig.ALBUM_USER_DISLIKE;
        String userFavKey = RedisConfig.ALBUM_USER_FAV;

        // 计数 key
        String key = RedisConfig.COUNT_KEY + type + ":" + albumId;

        Object loginId = StpUtil.getLoginId();

        // 用户行为判断
        boolean alreadyLikeExists = redisUtil.sIsMember(userLikeKey + loginId, albumId);
        boolean alreadyDislikeExists = redisUtil.sIsMember(userDislikeKey + loginId, albumId);
        boolean alreadyFavExists = redisUtil.sIsMember(userFavKey + loginId, albumId);

        // Hash 字段
        String hashLikeKey = RedisConfig.HASH_LIKE_KEY;
        String hashDislikeKey = RedisConfig.HASH_DISLIKE_KEY;
        String hashFavKey = RedisConfig.HASH_FAV_KEY;

        // 计数初始化
        Integer likeCount = 0;
        Integer dislikeCount = 0;
        Integer favCount = 0;

        // 先查 redis
        Map<Object, Object> countMap = redisUtil.hmGetAll(key);
        Object likesObj = countMap.get(hashLikeKey);
        Object disLikesObj = countMap.get(hashDislikeKey);
        Object favObj = countMap.get(hashFavKey);

        if (likesObj != null) {
            likeCount = Integer.parseInt(likesObj.toString());
        }
        if (disLikesObj != null) {
            dislikeCount = Integer.parseInt(disLikesObj.toString());
        }
        if (favObj != null) {
            favCount = Integer.parseInt(favObj.toString());
        }

        // 如果 Redis 没有数据（说明缓存未命中），去 DB 查
        if ((likesObj == null && disLikesObj == null && favObj == null)) {
            LambdaQueryWrapper<ActionCounts> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(ActionCounts::getMessageId, albumId);
            wrapper.eq(ActionCounts::getType, type);
            ActionCounts actionCounts = actionCountsService.getOne(wrapper);

            if (actionCounts != null) {
                likeCount = actionCounts.getLikeCount();
                dislikeCount = actionCounts.getDislikeCount();
                favCount = actionCounts.getFavoriteCount();

                // 回写 redis（避免下次再查 DB）
                redisUtil.hmSet(key, hashLikeKey, likeCount);
                redisUtil.hmSet(key, hashDislikeKey, dislikeCount);
                redisUtil.hmSet(key, hashFavKey, favCount);
            }
        }

        // 封装返回值
        ActionStatusVo actionStatusVo = new ActionStatusVo();
        actionStatusVo.setLiked(alreadyLikeExists);
        actionStatusVo.setDisliked(alreadyDislikeExists);
        actionStatusVo.setFavorited(alreadyFavExists);
        actionStatusVo.setLikeCount(likeCount);
        actionStatusVo.setDislikeCount(dislikeCount);
        actionStatusVo.setFavoriteCount(favCount);

        return actionStatusVo;
    }

    @Override
    public void reportProgress(Integer trackId, Integer currentTime, Integer duration) {
        int uid = StpUtil.getLoginIdAsInt();
        // 当前区间
        int index = currentTime / Constants.STEP;
        String key = RedisConfig.TRACK_PLAY + trackId + ":" + uid;
        // 在bitmap里设置该区间已覆盖
        BitMapUtils.setBit(key, index, true);
        // 这里可选：设置过期时间，避免key无限增长
        // 假设过期时间为音频时长的2倍
        long expireSeconds = duration * 2;
        redisUtil.expire(key, expireSeconds);
        double rate = getCompletionRate(uid, trackId, duration);
        if (rate >= 0.9) {
            saveCompletion(uid,trackId,rate,duration);
        }
    }

    @Override
    public void finalizePlayback(Integer trackId, Integer duration) {
        int uid = StpUtil.getLoginIdAsInt();

        double rate = getCompletionRate(uid, trackId, duration);

        saveDb(uid, trackId, rate,duration);
    }


    @Override
    public List<Integer> getActionAlbumIds() {
        return userActionMapper.getActionAlbumIds();
    }

    public double getCompletionRate(Integer userId, Integer trackId, int duration) {
        String key = RedisConfig.TRACK_PLAY + trackId + ":" + userId;
        int totalSegments = (int) Math.ceil(duration * 1.0 /Constants.STEP);
        Long covered = BitMapUtils.bitCount(key);
        if (covered == null || totalSegments == 0) {
            return 0.0;
        }
        return covered * 1.0 / totalSegments;
    }
    /**
     * 保存完播事件（只保存一次）
     */
    private void  saveCompletion(Integer userId, Integer trackId, double rate, Integer duration) {
        String lockKey = "audio:completed:" + trackId + ":" + userId;

        // 防止重复落库（只允许第一次成功）
        Boolean isNew = redisUtil.setNx(lockKey, "1", 86400);
        if (Boolean.FALSE.equals(isNew)) {
            return; // 已经保存过了
        }

        saveDb(userId, trackId, rate, duration);
    }

    private void saveDb(Integer userId, Integer trackId, double rate, Integer duration) {
        LambdaQueryWrapper<UserTrackPlayback> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserTrackPlayback::getUserId, userId);
        queryWrapper.eq(UserTrackPlayback::getTrackId, trackId);

        UserTrackPlayback userTrackPlayback = userTrackPlaybackMapper.selectOne(queryWrapper);
        if (userTrackPlayback != null && userTrackPlayback.getCompleteRate().compareTo(BigDecimal.valueOf(rate)) > 0) {
            // 这里需要做一个从数据库中与redis中的bitmap合并操作使用或运算将其合并，使其状态统一TODO
            return;
        }
        LambdaQueryWrapper<UserTrackPlayback> opsQueryWrapper = new LambdaQueryWrapper<>();
        opsQueryWrapper.eq(UserTrackPlayback::getUserId, userId);
        opsQueryWrapper.eq(UserTrackPlayback::getTrackId, trackId);
        Long n = userTrackPlaybackMapper.selectCount(opsQueryWrapper);
        TrackInfo trackInfo = trackInfoService.getById(trackId);
        if (userTrackPlayback == null){
            //新增操作
            BigDecimal completedViewRate = trackInfo.getCompletedViewRate();
            BigDecimal newRate = BigDecimal.valueOf(rate);
            BigDecimal currentAvgRate = addNew(completedViewRate, Math.toIntExact(n), newRate);
            trackInfo.setCompletedViewRate(currentAvgRate);
            trackInfo.setUpdatedAt(new Date());
            trackInfoService.updateById(trackInfo);
        }else {
            // 更新操作
            BigDecimal currentAvg = trackInfo.getCompletedViewRate();
            UserTrackPlayback playBack = userTrackPlaybackMapper.selectOne(opsQueryWrapper);
            BigDecimal oldRate = playBack.getCompleteRate();
            BigDecimal newRate = BigDecimal.valueOf(rate);
            BigDecimal res = updateExisting(currentAvg, Math.toIntExact(n), oldRate, newRate);
            trackInfo.setCompletedViewRate(res);
            trackInfo.setUpdatedAt(new Date());
            trackInfoService.updateById(trackInfo);
        }
        // 插入用户播放记录，并判断是否完播
        UserTrackPlayback record = new UserTrackPlayback();
        record.setUserId(userId);
        record.setTrackId(trackId);
        record.setCompleteRate(BigDecimal.valueOf(rate));
        record.setPlayTime((int) Math.ceil(rate * duration));
        record.setIsCompleted(rate >= 0.9 ? 1 : 0);
        userTrackPlaybackMapper.insertOrUpdate(record);
    }
    /**
     * 修改已有数据时的平均值更新
     *
     * @param currentAvg 当前平均值 x̄_n
     * @param n 已有的数据量 n
     * @param oldValue 被修改前的值 x_k
     * @param newValue 修改后的值 x_k'
     * @return 更新后的平均值 x̄_n'
     */
    public static BigDecimal updateExisting(BigDecimal currentAvg, int n, BigDecimal oldValue, BigDecimal newValue) {
        if (n <= 0) {
            throw new IllegalArgumentException("数据个数必须大于 0");
        }
        BigDecimal delta = newValue.subtract(oldValue);
        BigDecimal increment = delta.divide(BigDecimal.valueOf(n), 8, RoundingMode.HALF_UP);
        return currentAvg.add(increment);
    }
    /**
     * 新增数据时的增量平均数更新
     *
     * @param currentAvg 当前平均值 x̄_n
     * @param n 已有的数据量 n
     * @param newValue 新数据 x_{n+1}
     * @return 更新后的平均值 x̄_{n+1}
     */
    public static BigDecimal addNew(BigDecimal currentAvg, int n, BigDecimal newValue) {
        if (n <= 0) {
            // 没有历史数据，直接返回新值
            return newValue;
        }
        BigDecimal delta = newValue.subtract(currentAvg);
        BigDecimal increment = delta.divide(BigDecimal.valueOf(n + 1), 8, RoundingMode.HALF_UP);
        return currentAvg.add(increment);
    }
}




